package com.github.qianhe.sdk;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Map;
import java.util.Random;
import java.util.Set;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.configurationprocessor.json.JSONObject;

public class QHUtil {

    private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

    private static final Random RANDOM = new SecureRandom();

    /**
     * 验签 判断签名是否正确，必须包含sign字段，否则返回false。使用MD5签名。
     *
     * @param data Map类型数据
     * @param key API密钥
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean verifySign(Map<String, String> data, String key) throws Exception {
        return verifySign(data, key, QHConstants.SignType.MD5);
    }

    /**
     * 验签 判断签名是否正确，必须包含sign字段，否则返回false。
     *
     * @param data Map类型数据
     * @param key API密钥
     * @param signType 签名方式
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean verifySign(Map<String, String> data, String key, QHConstants.SignType signType) throws Exception {
        if (!data.containsKey(QHConstants.FIELD_SIGN) ) {
            return false;
        }
        String sign = data.get(QHConstants.FIELD_SIGN);
        return sign(data, key, signType).equals(sign);
    }

    /**
     * 生成签名
     *
     * @param data 待签名数据
     * @param key API密钥
     * @return 签名
     */
    public static String sign(final Map<String, String> data, String key) throws Exception {
        return sign(data, key, QHConstants.SignType.MD5);
    }

    /**
     * 生成签名. 注意，若含有sign_type字段，必须和signType参数保持一致。
     *
     * @param data 待签名数据
     * @param key API密钥
     * @param signType 签名方式
     * @return 签名
     */
    public static String sign(Map<String, String> data, String key, QHConstants.SignType signType) throws Exception {
        Set<String> keySet = data.keySet();
        String[] keyArray = keySet.toArray(new String[keySet.size()]);
        Arrays.sort(keyArray);
        StringBuilder sb = new StringBuilder();
        for (String k : keyArray) {
            if (k.equals(QHConstants.FIELD_SIGN)) {
                continue;
            }
            String value = data.get(k);
            // utf8 编码
            try {
                if(null==value) {
                    value = "";
                }
                value = URLEncoder.encode(value, QHConstants.CHARSET_NAME);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            if (value.trim().length() > 0) {
                // 参数值为空，则不参与签名
                sb.append(k).append(QHConstants.VALUE_SEPARATOR).append(data.get(k).trim()).append(QHConstants.FIELD_SEPARATOR);
            }
        }
        sb.append(QHConstants.FIELD_APP_SECRET).append(QHConstants.VALUE_SEPARATOR).append(key);
        if (QHConstants.SignType.MD5.equals(signType)) {
            return MD5(sb.toString()).toUpperCase();
        }
        else if (QHConstants.SignType.HMACSHA256.equals(signType)) {
            return HMACSHA256(sb.toString(), key);
        }
        else {
            throw new Exception(String.format("Invalid sign_type: %s", signType));
        }
    }

    /**
     * 获取随机字符串 Nonce Str
     *
     * @return String 随机字符串
     */
    public static String generateNonceStr() {
        char[] nonceChars = new char[32];
        for (int index = 0; index < nonceChars.length; ++index) {
            nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
        }
        return new String(nonceChars);
    }

    /**
     * 生成 MD5
     *
     * @param data 待处理数据
     * @return MD5结果
     */
    public static String MD5(String data) throws Exception {
        MessageDigest md = MessageDigest.getInstance(QHConstants.MD5);
        byte[] array = md.digest(data.getBytes(QHConstants.CHARSET_NAME));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }

    /**
     * 生成 HMACSHA256
     * @param data 待处理数据
     * @param key 密钥
     * @return 加密结果
     * @throws Exception
     */
    public static String HMACSHA256(String data, String key) throws Exception {
        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
        sha256_HMAC.init(secret_key);
        byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }

    /**
     * 日志
     * @return
     */
    public static Logger getLogger() {
        return LoggerFactory.getLogger("QH java sdk");
    }

    /**
     * 获取当前时间戳，单位秒
     * @return
     */
    public static long getCurrentTimestamp() {
        return System.currentTimeMillis()/1000;
    }

    /**
     * 获取当前时间戳，单位秒
     * @return
     */
    public static String getCurrentTimestampStr() {
        return String.valueOf(getCurrentTimestamp());
    }

    /**
     * 获取当前时间戳，单位毫秒
     * @return
     */
    public static long getCurrentTimestampMs() {
        return System.currentTimeMillis();
    }

    /************************* Jackson序列化 工具 ************************/
    public static class Json {
        // 加载速度太慢了，放在静态代码块中
        // private static final ObjectMapper objectMapper = new ObjectMapper();
        private static ObjectMapper objectMapper;

        /**
         * 设置一些通用的属性
         */
        static {
            objectMapper = new ObjectMapper();
            //java8增加了一套全新的日期时间类，Jackson对此也有支持。这些支持是以Jackson模块形式提供的，所以首先就是注册这些模块。
            objectMapper.registerModule(new JavaTimeModule())
                    .registerModule(new ParameterNamesModule())
                    .registerModule(new Jdk8Module())
                    // 通过该方法对mapper对象进行设置，所有序列化的对象都将按改规则进行系列化
                    // Include.Include.ALWAYS 默认
                    // Include.NON_DEFAULT 属性为默认值不序列化
                    // Include.NON_EMPTY 属性为 空（""） 或者为 NULL 都不序列化，则返回的json是没有这个字段的。这样对移动端会更省流量
                    // Include.NON_NULL 属性为NULL 不序列化
                    .setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
                    //允许对象忽略json中不存在的属性,避免位置属性 反序列化失败，默认忽略未知属性
                    .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
//			.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
                    //允许出现特殊字符和转义符
//				.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true)
                    //允许出现单引号
                    .configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);

            objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        }

        //单例
        public static ObjectMapper getInstance() {
            return objectMapper;
        }

        /**
         * 把Java对象转换成json字符串
         *
         * @param object 待转化为JSON字符串的Java对象
         * @return json 串 or null
         */
        public static String toJSONString(Object object) {
            try {
                return null!=object ? objectMapper.writeValueAsString(object) : "";
            } catch (JsonProcessingException e) {
                e.printStackTrace();
                return null;
            }
        }

        /**
         * 将Json字符串信息转换成对应的JSONObject对象
         *
         * @param jsonStr json字符串对象
         */
        public static JSONObject parse(String jsonStr) {
            return isNotBlank(jsonStr) ? objectMapper.convertValue(jsonStr, JSONObject.class) : null;
        }


        public static Map<String, Object> toMap(String jsonStr) {
            return isNotBlank(jsonStr) ? parse(jsonStr, Map.class) : null;
        }

        /**
         * 将Json字符串信息转换成对应的Java对象
         *
         * @param jsonStr json字符串对象
         * @param clazz   对应的类型
         */
        public static <T> T parse(String jsonStr, Class<T> clazz) {
            try {
                //避免位置属性 反序列化失败，默认忽略未知属性
                return isNotBlank(jsonStr) ? objectMapper.readValue(jsonStr, clazz) : null;
            } catch (JsonProcessingException e) {
                e.printStackTrace();
                return null;
            }
        }

        /**
         * 将 JSON 对象转换为 Java对象,可将Map转换成javaBean
         *
         * @param object json对象
         * @param clazz  对应的类型
         */
        public static <T> T parse(Object object, Class<T> clazz) {
            return null!=object ? objectMapper.convertValue(object, clazz) : null;
        }
    }

    /**
     * 判断字符数组是否为空
     */
    public static boolean isNotEmpty(String... values) {
        boolean result = true;
        if (values == null || values.length == 0) {
            result = false;
        } else {
            for (String value : values) {
                result &= !isEmpty(value);
            }
        }
        return result;
    }

    public static boolean isEmpty(final CharSequence cs) {
        return cs == null || cs.length() == 0;
    }

    public static boolean isNotEmpty(final CharSequence cs) {
        return !isEmpty(cs);
    }

    public static boolean isBlank(CharSequence cs) {
        int strLen;
        if (cs != null && (strLen = cs.length()) != 0) {
            for(int i = 0; i < strLen; ++i) {
                if (!Character.isWhitespace(cs.charAt(i))) {
                    return false;
                }
            }

            return true;
        } else {
            return true;
        }
    }

    public static boolean isNotBlank(CharSequence cs) {
        return !isBlank(cs);
    }
}
